/* 
 * Copyright 2014 Josef Hertl.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package caetus.pkcs11.template;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import sun.security.pkcs11.wrapper.CK_ATTRIBUTE;

/**
 * Represents a template for an object. Essentially the class consists of
 * CK_Attribute[] with many methods for template checks.
 *
 * @author Josef Hertl
 */
public class Template {

    private CK_ATTRIBUTE[] attributes;
    /**
     * A list used for higher-level preparation of attributes array - this
     * approach eliminates possible typos and makes automated testing much
     * easier.
     */
    private List<CK_ATTRIBUTE> preparedAttributes;
    private boolean preparationFinished = false;

    /**
     * Creates the template with attributes already prepared.
     *
     * @param attributes The attributes that will form the template.
     */
    public Template(CK_ATTRIBUTE[] attributes) {
        this.attributes = attributes;
    }

    /**
     * Prepares an empty template. Attributes will be be added to the template
     * by addAttribute() method. After the addition of attributes is finished,
     * the template must be completed using finishPreparation() method.
     */
    public Template() {
        preparedAttributes = new LinkedList<CK_ATTRIBUTE>();
    }

    /**
     * Creates a template with one given attribute. Others still can be normally
     * added.
     *
     * @param attribute The one attribute in this template.
     */
    public Template(CK_ATTRIBUTE attribute) {
        preparedAttributes = new LinkedList<CK_ATTRIBUTE>();
        preparedAttributes.add(attribute);
    }

    /**
     * This constructor should NOT be used, as it is dangerous and prone to
     * errors.
     *
     * @param numberOfAttributes The size of attribute field.
     */
    public Template(int numberOfAttributes) {
    }

    /**
     * Adds the attribute to the list of prepared attributes. DOES check for
     * collision - no two same attributes (even with different values) may be
     * added to the list. An exception is thrown if such an attempt is made.
     *
     * @param attribute The added attribute (CK_ATTRIBUTE)
     * @throws IllegalArgumentException When adding attribute that is already
     * included in the template.
     * @return True if no value was overridden, false otherwise.
     */
    public boolean addAttribute(CK_ATTRIBUTE attribute) throws IllegalArgumentException {
        if (preparationFinished) {
            throw new IllegalStateException("The attribute preparation has been finished. The attributes field cannot be changed anymore! You should create a new template instead.");
        }
        for (CK_ATTRIBUTE ck_attribute : preparedAttributes) {
            if (ck_attribute.type == attribute.type) {
                throw new IllegalArgumentException("Attempting to add an attribute to the template when an attribute of same type already exists. " + attribute.toString());
            }
        }
        return preparedAttributes.add(attribute);
    }

    public void removeAttribute(CK_ATTRIBUTE attribute) {
        if (preparationFinished) {
            throw new IllegalStateException("The attribute preparation has been finished. The attributes field cannot be changed anymore! You should create a new template instead.");
        }
        preparedAttributes.remove(attribute);
    }

    /**
     * Finishes the preparation of CK_ATTRIBUTE array. The array cannot be
     * changed from now on. Generally, this method should only be called when NO
     * other attributes should be added to the template.
     */
    public void finishPreparation() {
        if (preparedAttributes.isEmpty()) {
            throw new IllegalStateException("There are no prepared attributes! You must first add some attributes to the template, only afterwards can the finishPreparation() and getAttributes() methods be called!");
        }
        attributes = new CK_ATTRIBUTE[preparedAttributes.size()];
        for (int i = 0; i < attributes.length; i++) {
            attributes[i] = preparedAttributes.get(i);
        }
        preparationFinished = true;
    }

    /**
     * @return The CK_ATTRIBUTE array - the complete template itself.
     * Automatically attempts to call finishPreparation() if the attributes
     * array is empty.
     */
    public CK_ATTRIBUTE[] finishAndGetAttributes() {
        if (attributes == null) {
            finishPreparation();
        }
        return attributes;
    }

    /**
     * @return The list of prepared attributes without finishing the
     * preparation.
     */
    public List<CK_ATTRIBUTE> getPreparedAttributes() {
        return Collections.unmodifiableList(preparedAttributes);
    }

    /**
     * Merges the template into this template. If there is a collision, values
     * from this template take precedence.
     *
     * @param mergedIn The template whose values are merged into this one.
     */
    public void merge(Template mergedIn) {
        for (CK_ATTRIBUTE ck_attribute : mergedIn.getPreparedAttributes()) {
            try {
                addAttribute(ck_attribute);
            } catch (IllegalArgumentException iae) {
                //OK, nevermind - the attribute was already in.
            }
        }
    }

    @Override
    public String toString() {
        if (preparationFinished) {
            return attributes.toString();
        } else {
            return preparedAttributes.toString();
        }
    }
}
